/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.panomedic.core;

import com.panomedic.utils.Utils;
import com.panomedic.*;
import com.panomedic.colors.ColorConstants;
import com.panomedic.colors.Pixel;
import com.panomedic.core.blur.ConvolveFilter;
import com.panomedic.core.blur.GaussianFilter;
import com.panomedic.log4j.LogCategories;
import com.panomedic.log4j.Logger;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;

/**
 *
 * @author Yare
 */
public class HistogramRaster {

    static Logger logger = new Logger();
    private Preferences prefs;
    private Photo photo = null;
    private Raster raster = null;
    private int[] histogram = null;
    private double[] mean = null;
    private double[] sd = null;
    private int[] threshold = new int[3];
    private int[] range;
    private int[] minValue;
    private boolean[] lum;
    int csType = ColorConstants.HSV;    //DEBUG
    public BufferedImage src = null;
    public BufferedImage dst = null;

    /**
     * Histogram constructor creates histogram of picture bounded by 2 Points
     * and run the blurring
     * @param photo
     * @param topLeft
     * @param bottomRight
     */
    public HistogramRaster(Raster raster, Point topLeft, Point bottomRight) {
        this.raster = raster;
        
        prefs = Preferences.userRoot().node(Constants.defNodePath);
        csType = prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV);

        range = ColorConstants.getRange(csType);
        minValue = ColorConstants.getMin(csType);
        lum = ColorConstants.getLumComp(csType);
        for (int i = 0; i < range.length; i++) {
//            threshold[i] = range[i] / 20;
            threshold[i] = 0;
        }

        
//        histogram = new int[256];
//        for( int k = 0; k < 256; k++ ) 
//            histogram[k] = 0;
//        
//        for( int x = 0; x < raster.getWidth(); x++ )
//                for( int y = 0; y < raster.getHeight(); y++)
//                    histogram[raster.getSample(x, y, 0)]++;

    }

    /**
     * NEW JAVADOC
     * @param h2
     * @return
     */
    public double[] countGamma(HistogramRaster h2) {
        Raster r2 = h2.getRaster();

        double sum[] = new double[3];
        double count = 0;
        int px1[] = null;
        int px2[] = null;
        for (int x = 0; x < raster.getWidth(); x++) {
            for (int y = 0; y < raster.getHeight(); y++) {
                px1 = raster.getPixel(x, y, px1);
                px2 = r2.getPixel(x, y, px2);

                if (Utils.isPixelOpaque(px1) && Utils.isPixelOpaque(px2)) {
                    double[] array1 = Utils.getPixelValues(px1, csType);
                    double[] array2 = Utils.getPixelValues(px2, csType);

                    boolean isTaken = true;
                    for (int i = 0; i < 3; i++) {
                        if (lum[i] && (minValue[i] + array1[i] <= threshold[i] || range[i] - array1[i] <= threshold[i] || minValue[i] + array2[i] <= threshold[i] || range[i] - array2[i] <= threshold[i])) {
                            isTaken = false;
                        }
                    }
                    if (isTaken) {

                        for (int i = 0; i < 3; i++) {
                            double c1 = array1[i] / range[i];
                            double c2 = array2[i] / range[i];
                            double value = Math.log(c1) / Math.log(c2);
                            if (y == 50 && i == 0) {
                                logger.info(LogCategories.PROCESSING, "gamma " + count + ": " + value);
                            }
                            double maxGamma = 10;
                            if (value < maxGamma) {
                                sum[i] += value;
                            }
                        }
                        count++;
                    }
                }
            }
        }
        for (int i = 0; i < 3; i++) {
            sum[i] /= count;
        }
        logger.debug(LogCategories.PROCESSING, "[ DEBUG ] Average gamma: " + Arrays.toString(sum));
        return sum;

    }

    private double[] countMean(double[] gamma) {


        double sum[] = new double[3];
        double count = 0;
        int px[] = null;
        for (int x = 0; x < raster.getWidth(); x++) {
            for (int y = 0; y < raster.getHeight(); y++) {
                px = raster.getPixel(x, y, px);
                if (Utils.isPixelOpaque(px)) {
                    double[] array = Utils.getPixelValues(px, csType);
                    double[] value = new double[3];
                    boolean isTaken = true;
                    for (int i = 0; i < 3; i++) {
                        value[i] = Math.pow(array[i], gamma[i]);
                        if (lum[i] && (minValue[i] + value[i] <= threshold[i] || range[i] - value[i] <= threshold[i])) {
                            isTaken = false;
                        }
                    }
                    if (isTaken) {
                        for (int i = 0; i < 3; i++) {
                            sum[i] += value[i];
                        }
                        count++;
                    }
                }
            }
        }
        for (int i = 0; i < 3; i++) {
            sum[i] /= count;
        }
        return sum;
    }

    private double[] countSD(double[] mean, double[] gamma) {

        double sum[] = new double[3];
        double count = 0;
        int px[] = null;
        for (int x = 0; x < raster.getWidth(); x++) {
            for (int y = 0; y < raster.getHeight(); y++) {
                px = raster.getPixel(x, y, px);
                if (Utils.isPixelOpaque(px)) {
                    double[] array = Utils.getPixelValues(px, csType);
                    double[] value = new double[3];
                    boolean isTaken = true;
                    for (int i = 0; i < 3; i++) {
                        value[i] = Math.pow(array[i], gamma[i]);
                        if (lum[i] && (minValue[i] + value[i] <= threshold[i] || range[i] - value[i] <= threshold[i])) {
                            isTaken = false;
                        }
                    }
                    if (isTaken) {
                        for (int i = 0; i < 3; i++) {
                            double t = value[i] - mean[i];
                            sum[i] += Math.pow(t, 2);
                        }
                        count++;
                    }
                }
            }
        }
        for (int i = 0; i < 3; i++) {
            sum[i] /= count - 1;
            sum[i] = Math.sqrt(sum[i]);

//            if (px2 instanceof YxyPixel && sum[i] < 0.0001) {
//                 sum[i] = 1;
//            }
        }
        return sum;
    }

    /**
     * Counts global mean as average of local means counted locally in neighboring area.
     * Dimension of window is given by parameter. Iterative algotrithm uses a buffer 
     * for storing rows which are being processed. Within computing mean, first window 
     * column is always subtracted from the sum and first succeeding column is added up.
     * Variance has to be computed separately in each step.
     * 
     * @param rectSide
     * @return
     */
    private void countBoth(int area) {


        double sumMean[] = new double[3];
        double sumVar[] = new double[3];
        int count = 0;

        int width = raster.getWidth();
        Pixel[][] rowBuffer = new Pixel[width][area];

        // init buffer
        for (int x = 0; x < width; x++) {
            rowBuffer[x] = new Pixel[area];
            int[] px = null;
            for (int k = 0; k < area; k++) {
                px = raster.getPixel(x, k, px);
                rowBuffer[x][k] = Pixel.getPixel(csType, px);
            }
        }

        int bufferOffset = 0;
        int border = area / 2;
        for (int y = border; y < raster.getHeight() - 1 - border; y++, bufferOffset++) {
            int transCount = 0;
            double[] wsumMean = new double[3];
            double[] wsumVar = new double[3];

            // first window - mean
            for (int m = 0; m < area; m++) {
                for (int n = 0; n < area; n++) {
                    int nn = (n + bufferOffset) % area;
                    if (rowBuffer[m][nn].isOpaque()) {
                        wsumMean = Utils.addArray(wsumMean, rowBuffer[m][nn].getComp());
                    } else {
                        transCount++;
                    }
                }
            }
            if (transCount == 0) {
                // first window - variance
                for (int m = 0; m < area; m++) {
                    for (int n = 0; n < area; n++) {
                        int nn = (n + bufferOffset) % area;
                        double[] tmp = new double[3];
                        tmp = Utils.subtractArrays(rowBuffer[m][nn].getComp(), wsumMean);
                        tmp = Utils.powerArray(tmp, 2);
                        wsumVar = Utils.addArray(wsumVar, tmp);
                    }
                }
                double[] tmp = new double[3];
                tmp = Utils.divideArrays1(wsumMean, (int) Math.pow(area, 2));
                sumMean = Utils.addArray(sumMean, tmp);
                tmp = Utils.divideArrays1(wsumVar, (int) Math.pow(area, 2));        // or -1 ??
                tmp = Utils.sqrtArray(tmp);
                sumVar = Utils.addArray(sumVar, tmp);
                count++;
            }
            // other windows - iteration
            for (int x = border + 1; x < width - border - 1; x++) {
                // subtract first column (local mean)
                int m1 = x - (border + 1);
                for (int n = 0; n < area; n++) {
                    int nn = (n + bufferOffset) % area;
                    if (rowBuffer[m1][nn].isOpaque()) {
                        wsumMean = Utils.subtractArray(wsumMean, rowBuffer[m1][nn].getComp());
                    } else {
                        transCount--;
                    }
                }
                // add next column (local mean)
                int m2 = x + border;
                for (int n = 0; n < area; n++) {
                    int nn = (n + bufferOffset) % area;
                    if (rowBuffer[m2][nn].isOpaque()) {
                        wsumMean = Utils.addArray(wsumMean, rowBuffer[m2][nn].getComp());
                    } else {
                        transCount++;
                    }
                }
                // add to total sumMean
                if (transCount == 0) {
                    // iterations window - variance
                    wsumVar = Utils.setArray0(wsumVar);
                    for (int m = x - border; m <= x + border; m++) {
                        for (int n = 0; n < area; n++) {
                            int nn = (n + bufferOffset) % area;
                            double[] tmp = new double[3];
                            tmp = Utils.subtractArrays(rowBuffer[m][nn].getComp(), wsumMean);
                            tmp = Utils.powerArray(tmp, 2);
                            wsumVar = Utils.addArray(wsumVar, tmp);
                        }
                    }
                    double[] tmp = new double[3];
                    tmp = Utils.divideArrays1(wsumMean, (int) Math.pow(area, 2));
                    sumMean = Utils.addArray(sumMean, tmp);
                    tmp = Utils.divideArrays1(wsumVar, (int) Math.pow(area, 2));        // or -1 ??
                    tmp = Utils.sqrtArray(tmp);
                    sumVar = Utils.addArray(sumVar, tmp);
                    count++;
                }
            }
            // init new row
            if (y != raster.getHeight() - 2 - border) {
                for (int x = 0; x < width; x++) {
                    int[] px = null;
                    int k = bufferOffset % area;
                    px = raster.getPixel(x, y + border, px);
                    rowBuffer[x][k] = Pixel.getPixel(csType, px);
                }
            }
            bufferOffset++;
        }

        sumMean = Utils.divideArray1(sumMean, count);
        sumVar = Utils.divideArray1(sumVar, count);
        mean = sumMean;
        sd = sumVar;
    }

    public double[] getSD() {
        return sd;
    }

    public double[] getMean() {
        return mean;
    }

    public Photo getPhoto() {
        return photo;
    }

    public Raster getRaster() {
        return raster;
    }

    public void count(double[] gamma) {
        logger.info(LogCategories.PROCESSING, "Counting mean...");
        mean = countMean(gamma);
        logger.info(LogCategories.PROCESSING, "Counting sd...");
        sd = countSD(mean, gamma);
        logger.info(LogCategories.PROCESSING, "Done.");
    }

    public void count() {
        int area = 3;
        logger.info(LogCategories.PROCESSING, "Counting local mean and std dev (" + area + "x" + area + ") - " + getPhoto().toString() + "...");
//        double[] gamma = new double[]{0,0,0};
//        mean = countMean(gamma);
//        sd = countSD(mean, gamma);

        countBoth(area);
        logger.debug(LogCategories.PROCESSING, "mean=" + Arrays.toString(getMean()));
        logger.debug(LogCategories.PROCESSING, "std dev=" + Arrays.toString(getSD()));
        logger.info(LogCategories.PROCESSING, "Done.");
    }

    public static void main(String[] args) {

        // DEBUG blurring 

        try {
            File file = new File("D:\\School\\Rocnikovy projekt\\testimages\\result-len.png");
            BufferedImage srcImage = ImageIO.read(file);


            Photo photo = new Photo(srcImage);
//            HistogramRaster hist = new HistogramRaster(photo, new Point(0, 0), new Point(photo.getImage().getWidth(), photo.getImage().getHeight()));
//            srcImage = hist.src;

//        ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
//        ColorConvertOp op = new ColorConvertOp(colorSpace, null);
//        dstImage = op.filter(sourceImage, null);

//            BufferedImage dstImage = hist.dst;
//        dstImage = Utils.getCompatibleChildImage(srcImage, 100, 100, srcImage.getWidth() - 100, srcImage.getHeight()-100);
//        dstImage.setRGB(200, 200, Color.WHITE.getRGB());

//            ImageIO.write(srcImage, "png", new File("src.png"));
//            ImageIO.write(dstImage, "png", new File("dst.png"));

        } catch (IOException e) {
            System.err.println("ERRORRRRR");
        }
    }
}
